package com.feather.common.pojo.adapter;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.feather.common.pojo.PojoConfiguration;
import com.feather.common.pojo.PojoEntry;
import com.feather.common.pojo.PojoInfo;

/**
 * @author flogyin
 */
public abstract class ConfigurationDocument {
	public static final String	NAMESPACE		= "http://www.feather.com/common/lang/pojo/schema";
	public static final String	NAMING_ROOT		= "Pojoconfig";
	public static final String	NAMING_NAME		= "name";
	public static final String	NAMING_POJO		= "pojo";
	public static final String	NAMING_ID		= "id";
	public static final String	NAMING_FILE		= "file";
	public static final String	NAMING_CLASS	= "class";
	public static final String	NAMING_SUMMARY	= "summary";

	protected String			dir;
	protected String			filePath;

	public ConfigurationDocument(String path) {
		this.dir = new File(path).getParent();
		if (!this.dir.endsWith(File.separator)) {
			this.dir += File.separator;
		}
		this.filePath = path;
	}

	public ConfigurationDocument(File path) {
		this.dir = path.getParent();
		if (!this.dir.endsWith(File.separator)) {
			this.dir += File.separator;
		}
		this.filePath = path.getPath();
	}

	public String getDir() {
		return this.dir;
	}

	public String getFilePath() {
		return this.filePath;
	}

	public void parse(PojoConfiguration configuration) throws ParserConfigurationException, SAXException, IOException {
		Document doc = readDocument();
		if (doc == null) {
			return;
		}
		String name = doc.getDocumentElement().getAttribute(NAMING_NAME);
		configuration.setName(name);

		NodeList nodeList = doc.getElementsByTagNameNS(NAMESPACE, NAMING_POJO);
		if (nodeList == null) {
			return;
		}
		int length = nodeList.getLength();
		for (int i = 0; i < length; i++) {
			Node node = nodeList.item(i);
			if (!(node instanceof Element)) {
				continue;
			}

			PojoInfo pojoInfo = toPojoInfo(name, (Element) node);

			Object pojoInstance = null;
			File file = new File(pojoInfo.getFile());
			if (file.exists() && file.isFile()) {
				pojoInstance = getPojoInstance(pojoInfo.getFile());
			} else {
				pojoInstance = getPojoInstance(this.dir + pojoInfo.getFile());
			}

			if (pojoInstance == null) {
				throw new RuntimeException("\"" + pojoInfo.getId() + "\" instance " + pojoInfo.getClassImplements() + " is null.");
			}

			PojoEntry pojoEntry = new PojoEntry();
			pojoEntry.setInfo(pojoInfo);
			pojoEntry.setObject(pojoInstance);

			// pojo id 重复时, 仅第一个配置有效.
			String id = pojoInfo.getId();
			if (configuration.get(id) == null) {
				configuration.add(pojoEntry);
			}
		}
	}

	protected Document readDocument() throws ParserConfigurationException, SAXException, IOException {
		System.out.println("Configuration file: " + this.filePath);// debug

		FileInputStream fis = null;
		try {
			fis = new FileInputStream(this.filePath);

			DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
			// documentBuilderFactory.setIgnoringElementContentWhitespace(true);
			documentBuilderFactory.setNamespaceAware(true);
			DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
			return documentBuilder.parse(fis);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}

	public static PojoInfo toPojoInfo(String name, Element element) {
		String id = element.getAttribute(NAMING_ID);
		String file = element.getAttribute(NAMING_FILE);
		String _class = element.getAttribute(NAMING_CLASS);
		String summary = element.getAttribute(NAMING_SUMMARY);

		System.out.println(name + " found: \"" + id + "\" " + _class);// debug

		PojoInfo pojoInfo = new PojoInfo();
		pojoInfo.setId(id);
		pojoInfo.setFile(file);
		pojoInfo.setClassImplements(_class);
		pojoInfo.setSummary(summary);
		return pojoInfo;
	}

	public static void toPojoElement(PojoInfo info, Element aPojoElement) {
		aPojoElement.setAttribute(NAMING_ID, info.getId());
		aPojoElement.setAttribute(NAMING_FILE, info.getFile());
		aPojoElement.setAttribute(NAMING_CLASS, info.getClassImplements());
		aPojoElement.setAttribute(NAMING_SUMMARY, info.getSummary());
	}

	@SuppressWarnings("rawtypes")
	public void saveConfig(PojoConfiguration pojoConfiguration) throws ParserConfigurationException, TransformerConfigurationException, FileNotFoundException, TransformerException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document cfgDocument = builder.newDocument();
		// cfgDocument.setXmlVersion("1.0");
		Element root = cfgDocument.createElement(NAMING_ROOT);
		root.setAttribute("xmlns", NAMESPACE);
		root.setAttribute(NAMING_NAME, pojoConfiguration.getName());
		cfgDocument.appendChild(root);

		Map pojoEntryMap = pojoConfiguration.get();

		Iterator iterator = pojoEntryMap.entrySet().iterator();
		while (iterator.hasNext()) {
			Map.Entry mapEntry = (Map.Entry) iterator.next();

			// String id = (String)mapEntry.getKey();
			PojoEntry pojoEntry = (PojoEntry) mapEntry.getValue();
			if (pojoEntry.isTransient()) {
				continue;
			}

			PojoInfo pojoInfo = pojoEntry.getInfo();

			writePojoDocument(this.dir + pojoInfo.getFile(), pojoEntry.getObject());

			Element aPojoElement = cfgDocument.createElement(NAMING_POJO);
			toPojoElement(pojoInfo, aPojoElement);

			root.appendChild(aPojoElement);
		}

		writeConfigurationDocument(this.filePath, cfgDocument);
	}

	protected void writeConfigurationDocument(String filePath, Document cfgDocument) throws TransformerConfigurationException, FileNotFoundException, TransformerException {
		new File(filePath).getParentFile().mkdirs();

		Transformer transFormer = TransformerFactory.newInstance().newTransformer();
		DOMSource domSource = new DOMSource(cfgDocument);

		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream(filePath);
			transFormer.transform(domSource, new StreamResult(fos));
		} finally {
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		}
	}

	protected abstract Object getPojoInstance(String pojoFilePath) throws FileNotFoundException;

	protected abstract void writePojoDocument(String filePath, Object object) throws IOException;
}
